import { isObject, isString } from '@material/helpers';
import { useForkRef, useStateCallback } from '@material/hooks';
import { TComponent } from '@material/typeing';
import Taro from '@tarojs/taro';
import {
    CSSProperties,
    Children,
    FC,
    ReactElement,
    cloneElement,
    forwardRef,
    useEffect,
    useMemo,
    useRef,
    useState
} from 'react';
import { clsx } from 'react-runtime-clsx';

export const UNMOUNTED = 'unmounted';
export const EXITED = 'exited';
export const ENTERING = 'entering';
export const ENTERED = 'entered';
export const EXITING = 'exiting';
export const forceReflow = (node: HTMLElement) => node.scrollTop;
export type CssType =
    | ((node: HTMLElement) => void | CSSProperties | string)
    | CSSProperties
    | string;

export interface TransitionProps extends TComponent {
    visible?: boolean;
    hasTransition?: boolean;
    children: ReactElement;
    appear?: boolean; // 第一次挂载时执执行输入转换
    disappear?: boolean;
    unmountOnExit?: boolean; // 完成退出后卸载组件
    mountOnEnter?: boolean; // 开始时才安装组件
    enter?: CssType;
    entering?: CssType;
    entered?: CssType;
    exit?: CssType;
    exiting?: CssType;
    exited?: CssType;
    onTransitionEnd?: (e: TransitionEvent) => void;
}

const defaultProps = {
    visible: false,
    appear: false,
    unmountOnExit: false,
    mountOnEnter: false
};

const Transition = forwardRef<HTMLElement, TransitionProps>((props, ref) => {
    const {
        children,
        disappear,
        visible,
        appear,
        onTransitionEnd,
        unmountOnExit,
        mountOnEnter,
        enter,
        entering,
        entered,
        exit,
        exiting,
        exited,
        ...restProps
    } = { ...defaultProps, ...props };
    const nodeRef = useRef<HTMLElement>(null);
    const handleRef = useForkRef(nodeRef, ref);
    const [transitionStyle, setTransitionStyle] = useState({});
    const [transitionClass, setTransitionClass] = useState('');
    const [status, setStatus] = useStateCallback(() => {
        let initialStatus;
        if (visible) {
            if (appear) {
                initialStatus = EXITED;
            } else {
                initialStatus = ENTERED;
            }
        } else {
            if (disappear) {
                initialStatus = ENTERED;
            } else {
                if (unmountOnExit || mountOnEnter) {
                    initialStatus = UNMOUNTED;
                } else {
                    initialStatus = EXITED;
                }
            }
        }
        return initialStatus;
    });

    const changeStyles = (statusHanger: any, element: HTMLElement) => {
        if (typeof statusHanger === 'function') {
            const style = statusHanger(element);
            if (isString(style)) {
                setTransitionClass(style);
            }
            if (isObject(style)) {
                setTransitionStyle(style);
            }
        }
        if (isString(statusHanger)) {
            setTransitionClass(statusHanger);
        }
        if (isObject(statusHanger)) {
            setTransitionStyle(statusHanger);
        }
    };
    useEffect(() => {
        Taro.nextTick(() => {
            const nodeElement = nodeRef.current as HTMLElement;
            if (visible) {
                if (status === UNMOUNTED) {
                    setStatus(EXITED);
                    return;
                }
                if (status !== ENTERING && status !== ENTERED) {
                    changeStyles(enter, nodeElement);
                    if (mountOnEnter || unmountOnExit) {
                        forceReflow(nodeElement);
                    }
                    setStatus(ENTERING, () => {
                        changeStyles(entering, nodeElement);
                    });
                }
            } else {
                if (status === EXITED && unmountOnExit) {
                    setStatus(UNMOUNTED);
                    return;
                }
                if (status === ENTERING || status === ENTERED) {
                    changeStyles(exit, nodeElement);
                    setStatus(EXITING, () => {
                        changeStyles(exiting, nodeElement);
                    });
                }
            }
        });
    }, [
        visible,
        status,
        unmountOnExit,
        setStatus,
        mountOnEnter,
        enter,
        entering,
        exit,
        exiting,
        appear
    ]);

    const transitionHandle = function (e: any) {
        if (status === ENTERING) {
            setStatus(ENTERED, () => changeStyles(entered, nodeRef.current as HTMLElement));
        }
        if (status === EXITING) {
            setStatus(EXITED, () => changeStyles(exited, nodeRef.current as HTMLElement));
        }
        if (typeof onTransitionEnd === 'function') {
            onTransitionEnd(e);
        }
    };

    const TransitionComponent = useMemo(() => {
        if (status === UNMOUNTED) {
            return null;
        }
        const Component = cloneElement(Children.only(children), restProps);
        return Component;
    }, [status, children, restProps]);

    return TransitionComponent
        ? cloneElement(TransitionComponent, {
              ref: handleRef,
              onTransitionEnd: transitionHandle,
              className: clsx([TransitionComponent.props.className, transitionClass]),
              style: { ...TransitionComponent.props.style, ...transitionStyle }
          })
        : null;
});

Transition.displayName = 'Transition';

const KTransition: FC<TransitionProps> = function (props) {
    const { hasTransition = true, ...rest } = props;
    if (hasTransition) {
        return <Transition {...rest}></Transition>;
    }
    return props.children;
};
export default KTransition;
